package com.xmmj.baselibrary.network

import io.reactivex.schedulers.Schedulers
import io.reactivex.android.schedulers.AndroidSchedulers
import com.trello.rxlifecycle3.components.support.RxAppCompatActivity
import com.trello.rxlifecycle3.LifecycleTransformer
import androidx.lifecycle.LifecycleOwner
import com.trello.rxlifecycle3.android.ActivityEvent
import com.trello.rxlifecycle3.android.FragmentEvent
import com.xmmj.baselibrary.base.dialog.RxDialog
import okhttp3.ResponseBody
import com.trello.rxlifecycle3.components.support.RxFragment
import io.reactivex.*
import io.reactivex.functions.Function
import okhttp3.MultipartBody
import java.io.*
import java.lang.IllegalArgumentException

/**
 * RxJava 工具类
 */
object RxUtil {
    /**
     * 任务调度器：事件订阅在io线程，事件观察在主线程
     */
    fun <T> applySchedulers(): ObservableTransformer<T, T> {
        return ObservableTransformer { upstream: Observable<T> ->
            upstream.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
        }
    }

    /**
     * 绑定生命周期，防止内存泄漏
     *
     * @param activity 上下文
     */
    fun <T> bindToLifecycle(activity: RxAppCompatActivity): LifecycleTransformer<T> {
        return activity.bindToLifecycle()
    }

    /**
     * 绑定生命周期，防止内存泄漏
     *
     * @param fragment 上下文
     */
    fun <T> bindToLifecycle(fragment: RxFragment): LifecycleTransformer<T> {
        return fragment.bindToLifecycle()
    }

    /**
     * 绑定生命周期，防止内存泄漏
     */
    fun <T> bindUntilEvent(
        lifecycleOwner: LifecycleOwner?,
        event: ActivityEvent
    ): LifecycleTransformer<T> {
        return when (lifecycleOwner) {
            is RxAppCompatActivity -> {
                lifecycleOwner.bindUntilEvent(event)
            }
            else -> {
                throw IllegalArgumentException("View isn't RxAppCompatActivity")
            }
        }
    }

    /**
     * 绑定生命周期，防止内存泄漏
     */
    fun <T> bindUntilEvent(
        lifecycleOwner: LifecycleOwner?,
        event: FragmentEvent
    ): LifecycleTransformer<T> {
        return when (lifecycleOwner) {
            is RxFragment -> {
                lifecycleOwner.bindUntilEvent(event)
            }
            else -> {
                throw IllegalArgumentException("View isn't RxFragment or RxDialog")
            }
        }
    }

    /**
     * 绑定生命周期，防止内存泄漏
     *
     * @param lifecycleOwner 上下文
     */
    fun <T> bindToLifecycle(lifecycleOwner: LifecycleOwner?): LifecycleTransformer<T> {
        return if (lifecycleOwner is RxAppCompatActivity) {
            lifecycleOwner.bindToLifecycle()
        } else if (lifecycleOwner is RxFragment) {
            (lifecycleOwner as RxFragment).bindToLifecycle()
        } else if (lifecycleOwner is RxDialog) {
            lifecycleOwner.bindToLifecycle()
        } else {
            throw IllegalArgumentException("View isn't RxAppCompatActivity、RxFragment or RxDialog")
        }
    }

    /**
     * IO线程
     */
    fun ioThread(): Scheduler {
        return Schedulers.io()
    }

    /**
     * UI主线程
     */
    fun mainThread(): Scheduler {
        return AndroidSchedulers.mainThread()
    }

    /**
     * 文件下载转换工具，如果文件不存在，会创建文件；已存在，则被替换
     *
     *
     * 定义 retrofit 接口<br></br>
     * GET("https://github.com/binwin20/my.apk")<br></br>
     * Observable<ResponseBody> downloadFile();
    </ResponseBody> *
     *
     * 使用方式<br></br>
     * Api.downloadFile()<br></br>
     * .flatMap(RxUtils.downloadFlapMap(file))<br></br>
     * .compose(RxUtils.applySchedulers())<br></br>
     * .subscribe(progress -> showProgress(progress));
     *
     * @param saveFile 保存文件的地址
     */
    fun downloadFlapMap(saveFile: File): Function<in ResponseBody, out Observable<Int>> {
        return Function { responseBody: ResponseBody ->
            Observable.create(
                ObservableOnSubscribe { subscriber: ObservableEmitter<Int> ->
                    writeResponseBodyToDisk(
                        responseBody,
                        saveFile,
                        subscriber
                    )
                } as ObservableOnSubscribe<Int>)
                .compose(applySchedulers())
        }
    }

    private fun writeResponseBodyToDisk(
        body: ResponseBody,
        file: File,
        subscriber: ObservableEmitter<in Int>
    ) {
        var throwable: Throwable? = null
        try {
            var inputStream: InputStream? = null
            var outputStream: OutputStream? = null
            var lastTime = System.currentTimeMillis()
            try {
                val fileReader = ByteArray(4096)
                val fileSize = body.contentLength()
                var fileSizeDownloaded: Long = 0
                inputStream = body.byteStream()
                outputStream = FileOutputStream(file)
                while (true) {
                    val read = inputStream.read(fileReader)
                    if (read == -1) {
                        break
                    }
                    outputStream.write(fileReader, 0, read)
                    fileSizeDownloaded += read.toLong()
                    if (System.currentTimeMillis() - lastTime > 200) {
                        lastTime = System.currentTimeMillis()
                        subscriber.onNext((fileSizeDownloaded * 100 / fileSize).toInt())
                    }
                }
                outputStream.flush()
            } catch (e: IOException) {
                throwable = e
            } finally {
                inputStream?.close()
                outputStream?.close()
            }
        } catch (e: IOException) {
            throwable = e
        } finally {
            if (throwable == null) {
                subscriber.onComplete()
            } else {
                subscriber.onError(throwable)
            }
        }
    }

    /**
     * Retrofit 使用 OkHttp 上传文件时对 file 包装
     *
     * @param key  文件对应的key
     * @param path 文件路径
     */
    fun getRequestPart(
        key: String,
        path: String,
        callback: UploadRequestBody.UploadCallback
    ): MultipartBody.Part {
        val file = File(path)
        val body = UploadRequestBody(file, callback)
        // RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        return MultipartBody.Part.createFormData(key, file.name, body)
    }
}